package com.planw.beetl.service;


import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLiteralValue;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.planw.beetl.utils.BeetlSqlConst;
import com.planw.beetl.utils.ConstUtil;
import com.planw.beetl.utils.PsiKt;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.intellij.plugins.markdown.lang.MarkdownTokenTypes;
import org.intellij.plugins.markdown.lang.psi.impl.MarkdownHeader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * @author yanglin
 */
public class BeetlMapperService {

  private static final Logger logger = Logger.getInstance(BeetlMapperService.class);

  private final Project project;

  private final JavaPsiFacade javaPsiFacade;

  private final GlobalSearchScope javaSearchScope;

  private final GlobalSearchScope allSearchScope;

  public BeetlMapperService(Project project) {

    this.project = project;
    this.javaPsiFacade = JavaPsiFacade.getInstance(project);

    this.javaSearchScope = GlobalSearchScope.getScopeRestrictedByFileTypes(
        GlobalSearchScope.allScope(project), JavaFileType.INSTANCE);

    this.allSearchScope = GlobalSearchScope.allScope(project);

  }

  public static BeetlMapperService getInstance(@NotNull Project project) {

    return project.getService(BeetlMapperService.class);
  }


  public boolean isBeetlMapper(PsiClass psiClass) {

    if (!psiClass.isInterface()) {
      return false;
    }
    String baseMapperClassName = ConstUtil.getBaseMapperClassName(psiClass.getProject());
    // 接口只有单继承
    PsiClass parentInterface = ArrayUtil.getFirstElement(psiClass.getInterfaces());
    while (Objects.nonNull(parentInterface)) {
      //有可能对BaseMapper定制
      if (StringUtil.equals(parentInterface.getQualifiedName(), baseMapperClassName)) {
        return true;
      } else {
        parentInterface = ArrayUtil.getFirstElement(parentInterface.getInterfaces());
      }
    }
    return false;
  }

  /**
   * 获取所有继承了BaseMapper的接口
   */
  public Collection<PsiClass> getAllMappers(Module module) {

    if (Objects.isNull(module)) {
      return Collections.emptyList();
    }
    BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(module.getProject());
    PsiClass baseMapperPsiClass = beetlSqlConst.BASE_MAPPER_PSI_CLASS;

    return ClassInheritorsSearch.search(baseMapperPsiClass, GlobalSearchScope.moduleScope(module),
        true).findAll();
  }

  public Map<PsiMethod, PsiElement> getMethodAndSqlIdMap(PsiFile sqlFile, PsiClass psiClass,
      PsiMethod psiMethod) {

    logger.info("开始获取markdown文件的Header内容与Mapper接口中方法一一对应的Map");
    if (psiClass == null) {
      return Collections.emptyMap();
    }

    PsiMethod[] methods = psiMethod == null ? psiClass.getMethods() : new PsiMethod[]{psiMethod};
    if (methods.length == 0) {
      return Collections.emptyMap();
    }
    Map<String, PsiElement> sqlIdMap = new HashMap<>();
    Collection<MarkdownHeader> markdownHeaders = PsiTreeUtil.findChildrenOfType(
        sqlFile.getOriginalFile(), MarkdownHeader.class);
    for (MarkdownHeader markdownHeader : markdownHeaders) {
      ASTNode node = markdownHeader.getNode().findChildByType(MarkdownTokenTypes.SETEXT_CONTENT);
      if (node != null) {
        sqlIdMap.put(node.getText(), node.getPsi());
      }
    }
    logger.info("当前md文件的所有Header：" + sqlIdMap);
    Map<PsiMethod, PsiElement> methodAndSqlIdMap = new HashMap<>();
    for (PsiMethod method : methods) {
      String name = method.getName();
      PsiElement sqlId = sqlIdMap.get(name);
      if (sqlId != null) {
        methodAndSqlIdMap.put(method, sqlId);
      }
    }
    logger.info("最终一一对应的Map：" + methodAndSqlIdMap);
    return methodAndSqlIdMap;
  }

  public PsiFile findSqlFile(PsiClass mapperPsiClass) {

    String sqlFileName = guessMdFilePathByMapperClass(mapperPsiClass);

    if (StringUtils.isBlank(sqlFileName)) {
      return null;
    }

    // 获取当前的模块，缩小mapper只关联本模块的md文件
    return getBeetlSqlFile(ModuleUtil.findModuleForPsiElement(mapperPsiClass), sqlFileName);
  }

  @Nullable
  public String guessMdFilePathByMapperClass(PsiClass mapperPsiClass) {

    if (!this.isBeetlMapper(mapperPsiClass)) {
      return null;
    }
    BeetlSqlConst beetlSqlConst = ConstUtil.getBeetlSqlConst(
        mapperPsiClass.getContainingFile().getOriginalFile());
    String sqlFileName = null;
    // 继承的接口
    // SqlResource注解
    PsiAnnotation sqlResource = mapperPsiClass.getAnnotation(
        beetlSqlConst.SQL_RESOURCE_BEETL_CLASS);
    if (sqlResource != null) {
      PsiAnnotationMemberValue value = sqlResource.findDeclaredAttributeValue("value");
      PsiLiteralValue psiLiteralValue = (PsiLiteralValue) value;
      sqlFileName = psiLiteralValue != null ? (String) psiLiteralValue.getValue() : null;
      logger.info("存在SqlResource注解，value值为：" + sqlFileName);
    } else {
      PsiClassType[] parentInterfaces = mapperPsiClass.getExtendsListTypes();
      PsiClassType firstParentInterface = ArrayUtil.getFirstElement(parentInterfaces);
      if (Objects.isNull(firstParentInterface)) {
        return null;
      }
      // 没有注解，泛型类的名称首字母小写作为文件名
      // getInternalCanonicalText 内部标准文本表示，等价于全限定类名
      //getPresentableText, 呈现的文本，等于类名本身
      sqlFileName = firstParentInterface.getParameters()[0].getPresentableText();
      logger.info(
          "无SqlResource注解，将通过继承的BaseMapper接口编写的泛型猜测文件名：" + sqlFileName);
    }
    return sqlFileName;
  }

  public PsiFile getBeetlSqlFile(Module module, String className) {

    if (StringUtils.isBlank(className)) {
      return null;
    }
    String sqlFileName = StringUtils.uncapitalize(className).replaceAll("\\.", "/");
    // 优先md文件
    PsiFile sqlFile = loadSqlFile(module, sqlFileName + ".md");
    if (sqlFile != null) {
      return sqlFile;
    } else {
      sqlFile = loadSqlFile(module, sqlFileName + ".sql");
    }
    return sqlFile;
  }

  public boolean isPageRequest(String pageRequestClassName, PsiParameter parameter) {

    String className = PsiKt.getClassName(parameter.getType());
    return StringUtils.equals(pageRequestClassName, className);
  }

  public boolean isPageRequest(PsiParameter parameter) {

    String pageRequestClassName = ConstUtil.getPageRequestClassName(
        parameter.getContainingFile().getOriginalFile());
    String className = PsiKt.getClassName(parameter.getType());
    return StringUtils.equals(pageRequestClassName, className);
  }

  public PsiAnnotation getParamAnnotation(PsiParameter parameter) {

    String paramAnnotationClassName = ConstUtil.getParamAnnotationClassName(
        parameter.getContainingFile().getOriginalFile());
    return parameter.getAnnotation(paramAnnotationClassName);
  }

  public String getParamAnnotationValue(String paramAnnotationClassName, PsiParameter parameter) {

    PsiAnnotation annotation = parameter.getAnnotation(paramAnnotationClassName);
    if (annotation == null) {
      return null;
    }
    return Optional.ofNullable(annotation.findDeclaredAttributeValue("value"))
        .map(PsiElement::getText).orElse("").replaceAll("\"", "");
  }

  public String getParamAnnotationValue(PsiParameter parameter) {

    PsiAnnotation annotation = this.getParamAnnotation(parameter);
    if (annotation == null) {
      return null;
    }
    return Optional.ofNullable(annotation.findDeclaredAttributeValue("value"))
        .map(PsiElement::getText).orElse("").replaceAll("\"", "");
  }

  private PsiFile loadSqlFile(Module module, String sqlFilePath) {

    String fileName = Paths.get(sqlFilePath).getFileName().toString();
    Collection<VirtualFile> mdVirtualFiles = FilenameIndex.getVirtualFilesByName(fileName,
        GlobalSearchScope.moduleScope(module));
    PsiFile psiFile = null;
    for (VirtualFile mdFile : mdVirtualFiles) {
      if (mdFile.getPath().endsWith(sqlFilePath)) {
        psiFile = PsiUtil.getPsiFile(module.getProject(), mdFile);
        break;
      }
    }
    logger.info("在当前模块中找文件：" + sqlFilePath + "；查找结果：" + psiFile);
    return psiFile;
  }

}

